﻿using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Principal;
using System.Text;
using System.Web;
using System.Web.Security;
using Security;
using TripLOA.WebService.Serializable;
using TripLOA.WebService.Wrappers;

namespace TripLOA.WebService
{
    public class UserCore : WsCore
    {   
        #region Instance

        private static UserCore _instance;

        private UserCore() { }

        /// <summary>
        /// Get UserCore Instance
        /// </summary>
        public static UserCore Instance 
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new UserCore();
                }

                return _instance;
            }
        }

        #endregion
        
        #region User Methods

        /// <summary>
        /// User login
        /// </summary>
        /// <param name="username">The username</param>
        /// <param name="password">The password</param>
        /// <returns>True on login success, False otherwise</returns>
        public WSUser Login(string username, string password)
        {
            WSUser u = new WSUser();
            //Security
            if (Membership.ValidateUser(username, password))
            {
                u.Status = "ok";
                HttpCookie cookie = FormsAuthentication.GetAuthCookie(username, false);
                string token = cookie.Value;
                //Security.TripLoaUser user = Security.TripLoaUserManager._istance.getUser(username);
                TripLoaUser user = this.GetUserByAuthToken(token);
                u.AuthToken = token;
                u.Payload.Add(new UserInfo() { Username = username, UserID = user.UserID, NumericFeedbacks = this.GetUserNumericFeedback(user.GetProfile()) });
                //Calculate the vehicle list hash
                u.VehicleListHash = this.hashVehiclesList(this.GetVehicleList(user.GetProfile()));
                //Calculate the buddy list hash
                u.BuddyListHash = this.hashUserList(this.GetBuddyList(user.GetProfile()));
                //Calculate the banned list hash
                u.BannedListHash = this.hashUserList(this.GetBannedList(user.GetProfile()));
                //Calculate the notification list hash
                u.NotificationListHash = this.hashNotificationList(this.GetNotificationList(user.UserID));
            }
            else
            {
                u.Status = "fail";
                u.Message = "Invalid username or password.";
            }
            return u;
        }

        /// <summary>
        /// Authenticate an user by authentication token
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <returns>True on authentication success, False otherwise</returns>
        public bool Authenticate(string authToken)
        {
            FormsIdentity id = new FormsIdentity(FormsAuthentication.Decrypt(authToken));
            if (!id.IsAuthenticated)
                return false;

            HttpContext.Current.User = new GenericPrincipal(id, new string[] { "Members" });
            //return Security.TripLoaUserManager._istance.getUser(id.Name);

            return true;
        }

        /// <summary>
        /// Check AuthToken and fill WSObject with right status
        /// </summary>
        /// <param name="authToken">The Authentication Token</param>
        /// <param name="wso">WSObject to be filled</param>
        /// <returns>True if authentication success, false otherwise</returns>
        public bool checkAuth(string authToken, WSObject wso)
        {
            if (!this.Authenticate(authToken))
            {
                wso.Status = "fail";
                wso.Message = "Authentication failed";
                return false;
            }
            else
            {
                wso.Status = "ok";
                return true;
            }

        }

        /// <summary>
        /// Get user by authentication token
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <returns>If user is authenticated return the logged user, else throws an AuthenticationException</returns>
        public TripLoaUser GetUserByAuthToken(string authToken)
        {
            //Set User identity using authToken
            if (!this.Authenticate(authToken))
            {
                throw new System.Security.Authentication.AuthenticationException("User is not authenticated");
            }

            TripLoaUser tlu = TripLoaUserManager.Instance.getUser(HttpContext.Current.User.Identity.Name);
            return tlu;
        }

        /// <summary>
        /// Get the session logged user
        /// </summary>
        /// <returns>If user is authenticated return the logged user, else throws an AuthenticationException</returns>
        public TripLoaUser GetLoggedUser()
        {
            if (!HttpContext.Current.User.Identity.IsAuthenticated)
            {
                throw new System.Security.Authentication.AuthenticationException("User is not authenticated");
            }

            TripLoaUser tlu = TripLoaUserManager.Instance.getUser(HttpContext.Current.User.Identity.Name);
            return tlu;
        }
        
        /// <summary>
        /// Get an user profile given the user Id
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="userId">The user Id</param>
        /// <returns>The user profile</returns>
        public WSUserProfile GetProfile(string authToken, int userId)
        {
            WSUserProfile up = new WSUserProfile();
            //Check user auth, if fail return the default message error
            if (!this.checkAuth(authToken, up)) return up;
            
            up.Status = "ok";
            TripLoaUser user = this.GetUserByAuthToken(authToken);
            try
            {
                ProfileInfo pi = new ProfileInfo(user.GetProfile(userId));
                up.Payload.Add(pi);
            }
            catch (System.Exception)
            {
                //Can't create a ProfileInfo object
                up.Status = "fail";
                up.Message = "Error during profile creation.";
                return up;
            }
            return up;
        }

        /// <summary>
        /// Get logged user vehicle list
        /// </summary>
        /// <param name="authToken"></param>
        /// <param name="userId"></param>
        /// <returns>The serializable list of vehicles</returns>
        public WSUserVehicle GetUserVehicleList(string authToken)
        {
            WSUserVehicle uv = new WSUserVehicle();
            if (this.Authenticate(authToken))
            {
                uv.Status = "ok";
                TripLoaUser user = this.GetUserByAuthToken(authToken);
                try
                {
                    uv.Payload = this.GetVehicleList(user.GetProfile());
                    uv.Hash = this.hashVehiclesList(uv.Payload);
                }
                catch (System.Exception e)
                {
                    uv.Status = "fail";
                    uv.Message = "Error during vehicle list creation: "+e.Message;
                }
            }
            return uv;
        }

        /// <summary>
        /// Get logged user buddy list
        /// </summary>
        /// <param name="authToken"></param>
        /// <returns>The serializable list of users' friends</returns>
        public WSUser GetBuddyList(string authToken)
        {
            WSUser bl = new WSUser();
            if (!this.checkAuth(authToken, bl)) return bl;

            bl.Status = "ok";
            TripLoaUser user = this.GetUserByAuthToken(authToken);
            try
            {
                bl.Payload = this.GetBuddyList(user.GetProfile());
                bl.BuddyListHash = this.hashUserList(bl.Payload);

            }
            catch (System.Exception e)
            {
                bl.Status = "fail";
                bl.Message = "Error during user list creation: " + e.Message;
            }
            return bl;
        }

        /// <summary>
        /// Get logged user banned list
        /// </summary>
        /// <param name="authToken"></param>
        /// <returns>The serializable list of user's banned persons</returns>
        public WSUser GetBannedList(string authToken)
        {
            WSUser bl = new WSUser();
            if (!this.checkAuth(authToken, bl)) return bl;

            bl.Status = "ok";
            TripLoaUser user = this.GetUserByAuthToken(authToken);
            try
            {
                bl.Payload = this.GetBannedList(user.GetProfile());
                bl.BannedListHash = this.hashUserList(bl.Payload);

            }
            catch (System.Exception e)
            {
                bl.Status = "fail";
                bl.Message = "Error during user list creation: " + e.Message;
            }
            return bl;
        }

        /// <summary>
        /// Get logged user notifications list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <returns>The serializable list of notifications</returns>
        public WSNotification GetUserNotifications(string authToken)
        {
            WSNotification wsn = new WSNotification();
            if (this.Authenticate(authToken))
            {
                wsn.Status = "ok";
                TripLoaUser user = this.GetUserByAuthToken(authToken);
                try
                {
                    wsn.Payload = this.GetNotificationList(user.UserID);
                }
                catch (System.Exception e)
                {
                    wsn.Status = "fail";
                    wsn.Message = "Error during notifications list creation: " + e.Message;
                }
               
            }
            return wsn;
        }

        /// <summary>
        /// Get logged user friendship request list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <returns>The list of requests</returns>
        public WSUser GetUserFriendshipRequests(string authToken)
        {
            WSUser wsu = new WSUser();
            if (this.checkAuth(authToken, wsu))
            {
                TripLoaUser user = this.GetLoggedUser();
                wsu.Status = "ok";
                try
                {
                    wsu.Payload = this.GetFriendshipRequests(user.UserID);
                }
                catch (System.Exception ex)
                {
                    wsu.Status = "fail";
                    wsu.Message = "Error during friendship request list creation: " + ex.Message;
                }
            }

            return wsu;
        }

        /// <summary>
        /// Get logged user friendship notification list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <returns>The list of notification</returns>
        public WSUser GetUserFriendshipNotifications(string authToken)
        {
            WSUser wsu = new WSUser();
            if (this.checkAuth(authToken, wsu))
            {
                TripLoaUser user = this.GetLoggedUser();
                wsu.Status = "ok";
                try
                {
                    wsu.Payload = this.GetFriendshipNotifications(user.UserID);
                }
                catch (System.Exception ex)
                {
                    wsu.Status = "fail";
                    wsu.Message = "Error during friendship request list creation: " + ex.Message;
                }
            }

            return wsu;
        }

        /// <summary>
        /// Add a friend to buddy list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="friendId">The friend user Id</param>
        /// <returns>True on success, false otherwise</returns>
        public WSBool AddFriend(string authToken, int friendId)
        {
            WSBool wsb = new WSBool();
            if (!this.checkAuth(authToken, wsb)) return wsb;

            TripLoaUser user = this.GetLoggedUser();

            try
            {
                user.AddFriend(friendId);
                wsb.Status = "ok";
                wsb.Payload = true;
            }
            catch (System.Exception ex)
            {
                wsb.Status = "fail";
                wsb.Payload = false;
                wsb.Message = "Cannot add user to buddy list: " + ex.Message;
            }

            return wsb;
        }

        /// <summary>
        /// Accept a friendship request
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="friendId">The friend Id</param>
        /// <returns>True if success, False otherwise</returns>
        public WSBool AcceptFriendshipRequest(string authToken, int friendId)
        {
            WSBool wsb = new WSBool();
            if (!this.checkAuth(authToken, wsb)) return wsb;

            TripLoaUser user = this.GetLoggedUser();
            List<Groupware.FriendshipRequest> frList = Groupware.NotificationManager.getIncomingFriendshipRequestsByUserID(user);
            var fReqList = from req in frList
                       where req.AskingUserID == friendId
                       select req;
            if (fReqList.Count() == 1)
            {
                Groupware.FriendshipRequest fReq = fReqList.First();
                try
                {
                    fReq.AcceptFriendship(user);
                    wsb.Status = "ok";
                    wsb.Payload = true;
                }
                catch (System.Exception ex)
                {
                    wsb.Status = "fail";
                    wsb.Payload = false;
                    wsb.Message = "Cannot accept friendship request: " + ex.Message;
                }
            }
            else
            {
                wsb.Status = "fail";
                wsb.Payload = false;
                wsb.Message = "Cannot accept friendship request";
            }

            return wsb;
        }

        /// <summary>
        /// Deny a friendship request
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="friendId">The friend Id</param>
        /// <returns>True if success, False otherwise</returns>
        public WSBool DenyFriendshipRequest(string authToken, int friendId)
        {
            WSBool wsb = new WSBool();
            if (!this.checkAuth(authToken, wsb)) return wsb;

            TripLoaUser user = this.GetLoggedUser();
            List<Groupware.FriendshipRequest> frList = Groupware.NotificationManager.getIncomingFriendshipRequestsByUserID(user);
            var fReqList = from req in frList
                           where req.AskingUserID == friendId
                           select req;
            if (fReqList.Count() == 1)
            {
                Groupware.FriendshipRequest fReq = fReqList.First();
                try
                {
                    fReq.DenyFriendship(user);
                    wsb.Status = "ok";
                    wsb.Payload = true;
                }
                catch (System.Exception ex)
                {
                    wsb.Status = "fail";
                    wsb.Payload = false;
                    wsb.Message = "Cannot deny friendship request: " + ex.Message;
                }
            }
            else
            {
                wsb.Status = "fail";
                wsb.Payload = false;
                wsb.Message = "Cannot deny friendship request";
            }

            return wsb;
        }

        /// <summary>
        /// Remove a friend from buddy list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="friendId">The friend user Id</param>
        /// <returns>True on success, false otherwise</returns>
        public WSBool RemoveFriend(string authToken, int friendId)
        {
            WSBool wsb = new WSBool();
            if (!this.checkAuth(authToken, wsb)) return wsb;

            TripLoaUser user = this.GetLoggedUser();

            try
            {
                user.RemoveFriend(friendId);
                wsb.Status = "ok";
                wsb.Payload = true;
            }
            catch (System.Exception ex)
            {
                wsb.Status = "fail";
                wsb.Payload = false;
                wsb.Message = "Cannot add user to buddy list: " + ex.Message;
            }

            return wsb;
        }

        /// <summary>
        /// Ban a friend in buddy list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="friendId">The friend user Id</param>
        /// <returns>True on success, false otherwise</returns>
        public WSBool BanFriend(string authToken, int friendId)
        {
            WSBool wsb = new WSBool();
            if (!this.checkAuth(authToken, wsb)) return wsb;

            TripLoaUser user = this.GetLoggedUser();

            try
            {
                user.BanUser(friendId);
                wsb.Status = "ok";
                wsb.Payload = true;
            }
            catch (System.Exception ex)
            {
                wsb.Status = "fail";
                wsb.Payload = false;
                wsb.Message = "Cannot add user to buddy list: " + ex.Message;
            }

            return wsb;
        }

        /// <summary>
        /// Unban a friend in buddy list
        /// </summary>
        /// <param name="authToken">The authentication token</param>
        /// <param name="friendId">The friend user Id</param>
        /// <returns>True on success, false otherwise</returns>
        public WSBool UnbanFriend(string authToken, int friendId)
        {
            WSBool wsb = new WSBool();
            if (!this.checkAuth(authToken, wsb)) return wsb;

            TripLoaUser user = this.GetLoggedUser();

            try
            {
                user.UnbanUser(friendId);
                wsb.Status = "ok";
                wsb.Payload = true;
            }
            catch (System.Exception ex)
            {
                wsb.Status = "fail";
                wsb.Payload = false;
                wsb.Message = "Cannot add user to buddy list: " + ex.Message;
            }

            return wsb;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Calculate numeric feedback given User Profile
        /// </summary>
        /// <param name="profile">User Profile</param>
        /// <returns>Average of general votes in user feedbacks</returns>
        public decimal GetUserNumericFeedback(Security.Profile profile)
        {
            if (profile.Feedbacks != null && profile.Feedbacks.Count > 0)
            {
                return (decimal)(from f in profile.Feedbacks select f.generalVote).Average();
            }

            return 0;
        }

        /// <summary>
        /// Get a user vehicle list
        /// </summary>
        /// <param name="userProfile">User profile</param>
        /// <returns>The vehicle list</returns>
        private List<VehicleInfo> GetVehicleList(Profile userProfile)
        {
            List<VehicleInfo> vList = new List<VehicleInfo>();

            List<Storage.EntityModels.Users_VehicleDetails> vehicleList = userProfile.UsersVehiclesDetails;
            foreach (Storage.EntityModels.Users_VehicleDetails vehicle in vehicleList)
            {
                //VehicleInfo vi = new VehicleInfo(vehicle.VehicleDetails);
                //anonymizer
                VehicleInfo vi = new VehicleInfo(vehicle);
                vList.Add(vi);
            }

            return vList;
        }

        /// <summary>
        /// Calculate the MD5 hash of user vehicle list
        /// </summary>
        /// <param name="vehiclesList">The user vehicle list</param>
        /// <returns>The MD5 hash string</returns>
        private string hashVehiclesList(List<VehicleInfo> vehiclesList)
        {
            //If list is empty return empty string
            if (vehiclesList.Count == 0)
            {
                return string.Empty;
            }
            
            StringBuilder sb = new StringBuilder();

            foreach (var v in vehiclesList)
            {
                sb.Append ("," + v.Id.ToString());
            }

            sb.Remove(0, 1);

            var bytes = ASCIIEncoding.ASCII.GetBytes(sb.ToString());
            var hashedBytes = MD5.Create().ComputeHash(bytes);

            sb = new StringBuilder();
            foreach (var h in hashedBytes)
            {
                sb.Append(h.ToString("X2"));
            }
            return sb.ToString();
        }

        /// <summary>
        /// Calculate the MD5 hash of user notification list
        /// </summary>
        /// <param name="vehiclesList">The user notification list</param>
        /// <returns>The MD5 hash string</returns>
        private string hashNotificationList(List<Notification> notificationList)
        {
            //If list is empty return empty string
            if (notificationList.Count == 0)
            {
                return string.Empty;
            }

            StringBuilder sb = new StringBuilder();

            foreach (var n in notificationList)
            {
                sb.Append("," + n.EventId.ToString());
            }

            sb.Remove(0, 1);

            var bytes = ASCIIEncoding.ASCII.GetBytes(sb.ToString());
            var hashedBytes = MD5.Create().ComputeHash(bytes);

            sb = new StringBuilder();
            foreach (var h in hashedBytes)
            {
                sb.Append(h.ToString("X2"));
            }
            return sb.ToString();
        }

        /// <summary>
        /// Get a user notifications list
        /// </summary>
        /// <param name="userID"></param>
        /// <returns>The notifications list</returns>
        private List<Notification> GetNotificationList(int userId)
        {
            List<Notification> wsNotificationsList = new List<Notification>();
            List<Groupware.Notification> notificationsList = Groupware.NotificationManager.getNotifications(userId);
            foreach (Groupware.Notification not in notificationsList)
            {
                wsNotificationsList.Add(new Notification(not));
            }
            return wsNotificationsList;
        }

        /// <summary>
        /// Get all user friendship requests 
        /// </summary>
        /// <param name="userId">The ID of owner user</param>
        /// <returns>A list of all requests</returns>
        private List<UserInfo> GetFriendshipRequests(int userId)
        {
            List<UserInfo> frList = new List<UserInfo>();
            TripLoaUser loggedUser = this.GetLoggedUser();
            List<Groupware.FriendshipRequest> gwList = Groupware.NotificationManager.getIncomingFriendshipRequestsByUserID(loggedUser);
            foreach (var req in gwList)
            {
                UserInfo ui = new UserInfo(loggedUser.GetProfile(req.AskingUserID));
                frList.Add(ui);
            }

            return frList;
        }

        /// <summary>
        /// Get all user friendship notifications 
        /// </summary>
        /// <param name="userId">The ID of owner user</param>
        /// <returns>A list of all notifications</returns>
        private List<UserInfo> GetFriendshipNotifications(int userId)
        {
            List<UserInfo> frList = new List<UserInfo>();
            TripLoaUser loggedUser = this.GetLoggedUser();
            List<Groupware.FriendshipNotification> gwList = Groupware.NotificationManager.getFriendshipNotificationsByUserID(loggedUser);
            foreach (var not in gwList)
            {
                UserInfo ui = new UserInfo(loggedUser.GetProfile(not.NewFriendID));
                frList.Add(ui);
            }

            return frList;
        }

        /// <summary>
        /// Get User buddy list given an user profile
        /// </summary>
        /// <param name="userProfile">The user profile</param>
        /// <returns>The buddy list</returns>
        private List<UserInfo> GetBuddyList(Profile userProfile)
        {
            List<UserInfo> uList = new List<UserInfo>();
            foreach (var p in userProfile.BuddyList)
            {
                uList.Add(new UserInfo(p));
            }

            return uList;
        }

        /// <summary>
        /// Get User banned list given an user profile
        /// </summary>
        /// <param name="userProfile">The user profile</param>
        /// <returns>The banned list</returns>
        private List<UserInfo> GetBannedList(Profile userProfile)
        {
            List<UserInfo> uList = new List<UserInfo>();
            foreach (var p in userProfile.BannedList)
            {
                uList.Add(new UserInfo(p));
            }

            return uList;
        }

        /// <summary>
        /// Calculate the MD5 hash of user buddy list
        /// </summary>
        /// <param name="userList">The user buddy list</param>
        /// <returns>The MD5 hash string</returns>
        private string hashUserList(List<UserInfo> userList)
        {
            //If list is empty return empty string
            if (userList.Count == 0)
            {
                return string.Empty;
            }

            StringBuilder sb = new StringBuilder();

            foreach (var u in userList)
            {
                sb.Append("," + u.Username);
            }

            sb.Remove(0, 1);

            var bytes = ASCIIEncoding.ASCII.GetBytes(sb.ToString());
            var hashedBytes = MD5.Create().ComputeHash(bytes);

            sb = new StringBuilder();
            foreach (var h in hashedBytes)
            {
                sb.Append(h.ToString("X2"));
            }
            return sb.ToString();
        }

        
        #endregion
    }
}
